package service;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

public class PhpSerializationServiceImpl implements PhpSerializationService {
	
	public String serialize(Map<?, ?> unserialized) {
		String parsedMap = "";
		
		for (Map.Entry<?, ?> entry : unserialized.entrySet()) {
			Class<?> clazz = entry.getValue().getClass();
			
			if (clazz.equals(HashMap.class)) {
				// value is a map
				parsedMap += this.serializeString((String) entry.getKey()) + this.serializeMap((Map<Serializable, Serializable>) entry.getValue());
			} else if (clazz.equals(String.class)) {
				// value is a string
				parsedMap += this.serializeString((String) entry.getKey()) + this.serializeString((String) entry.getValue());
			} else if (clazz.equals(Integer.class)) {
				// value is an integer
			}
		}
		
		return parsedMap;
	}
	
	public Map<Serializable, Serializable> unserialize(String serialized) {
		Map<Serializable, Serializable> unserialized = new HashMap<Serializable, Serializable>();
		Serializable key = null;
		Serializable value = null;
		
		char[] chars = serialized.toCharArray();
		int i = 0;
		boolean isExpectingBuffer = false;
		boolean isParsingString = false;
		boolean isParsingArray = false;
		boolean isParsingLength = false;
		boolean isParsingKey = true;
		
		String piece = "";
		Integer length = 0;
		
		while (i < chars.length) {
			char character = chars[i];
			
			if (isExpectingBuffer) {
				// ignore some nonsense character
				isExpectingBuffer = false;
			} else if (!isExpectingBuffer) {
				if (isParsingString) {
					// parsing a string
					if (isParsingLength) {
						// parsing the length of the string
						if (character == ':') {
							length = Integer.parseInt(piece);
							piece = "";
							isExpectingBuffer = true;
							isParsingLength = false;
						} else {
							// keep parsing length
							piece += character;
						}
					} else {
						// parsing content of string
						for (int j = i; j < i + length; j += 1) {
							piece += chars[j];
						}
						
						if (isParsingKey) {
							// set the key to the string
							key = piece;
							isParsingKey = false;
						} else {
							// set the value to the string
							value = piece;
							unserialized.put(key, value);
							isParsingKey = true;
						}
						
						isParsingString = false;
						isExpectingBuffer = true;
						piece = "";
						i += length;
					}
				} else if (isParsingArray) {
					// parsing an array
					if (isParsingLength) {
						// parsing the length of the array
						if (character == ':') {
							length = Integer.parseInt(piece);
							piece = "";
							isParsingLength = false;
						} else {
							// keep parsing length
							piece += character;
						}
					} else if (character == '{') {
						// move to the next character
						i++;
						
						// locate the end of this array
						int j = i;
						int nestedBraces = 0;
						
						while (chars[j] != '}' || (chars[j] == '}' && nestedBraces == 0)) {
							j++;
							
							if (chars[j] == '{') {
								nestedBraces++;
							} else if (chars[j] == '}') {
								nestedBraces--;
							}
						}
						
						// create temp collection of all characters from this array to parse
						char[] tempchars = new char[j - i];
						
						for (int k = i; k < j; k++) {
							tempchars[k - i] = chars[k];
						}
						
						// use recursion to parse this array
						value = (Serializable) this.unserialize(new String(tempchars));
						
						// add new map
						unserialized.put(key, value);
						
						// move global position to end of array
						i = j;
						isParsingArray = false;
						isParsingKey = true;
					}
				} else {
					// not sure what to expect next
					if (character == 's') {
						// about to parse string
						isExpectingBuffer = true;
						isParsingString = true;
						isParsingLength = true;
					} else if (character == 'a') {
						// about to parse array
						isExpectingBuffer = true;
						isParsingArray = true;
						isParsingLength = true;
					}
				}
			}
			
			i++;
		}
		
		return unserialized;
	}
	
	private String serializeString(String string) {
		return "s:" + string.length() + ":\"" + string + "\";";
	}
	
	private String serializeMap(Map<Serializable, Serializable> map) {
		return "a:" + map.size() + ":{" + this.serialize(map) + "}";
	}

}
